package tcpClient;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.LinkedList;
import java.util.Queue;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.log4j.BasicConfigurator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.util.encoders.Hex;

import channels.AESChannel;
import channels.Channel;
import channels.SecureChannel;
import channels.TCPChannel;

import service.SecurityService;

public class AuctionClient implements ClientTestInterface{

	private Scanner stdin=null;
	private static int tcpport=12110;
	private static int serverport=12110;
	private static String serverPublicKeyPath;
	private static String clientKeysPath;
	//private static int udpport=12110;
	private boolean isActive = true;
	private static String hostname = null;
	private static AuctionClient auctionClient = null;
	private AuctionClientThread outputThread;
	//private UDPOutputThread udpOutputThread;
	//private DatagramSocket udpclient = null;
	private Channel myChannel;
	private SecurityService securityService;
	private boolean loggedIn=false;
	private boolean loggingIn=false;
	private PrivateKey clientKey;
	private PublicKey serverKey;
	private byte[] clientChallenge;
	private byte[] serverChallenge;
	private Key hmacKey;
	private Mac hmac;
	private String lastMessage;
	private boolean keysOk = true;
	private boolean isBlocked = false;
	private int blockCounter = 0;
	private String username = null;
	private int duration = 0;
	private volatile boolean stop = true;
	
	
	public static void main(String args[]) {
		//BasicConfigurator.configure();
		if(checkInput(args)==false) {
			usage();
		}	
		
		auctionClient = new AuctionClient(hostname,tcpport);
		auctionClient.run();
	}

	public AuctionClient(String hostname1, int port1) {
		hostname=hostname1;
		tcpport=port1;
		this.myChannel = TCPChannel.getInstance();
		this.securityService = SecurityService.getInstance();

	}


	public boolean initSocket() {

		return myChannel.open(hostname, serverport);
	}
	


	private void run() {
		stdin = new Scanner(System.in);

		String userinput = null;

		/*try {
			udpclient = new DatagramSocket(udpport);

		} catch (SocketException e1) {
			System.out.println(e1.getMessage());
			return;
		}*/


		if(!initSocket()){
			return;
		}


		outputThread = new AuctionClientThread(myChannel,this);
		//udpOutputThread = new UDPOutputThread(udpclient,hostname);
		//udpOutputThread.start();
		outputThread.start();



		while (isActive) {

			if(loggedIn) myChannel = AESChannel.getInstance();
			else if(loggingIn) myChannel = SecureChannel.getInstance();
			else myChannel = TCPChannel.getInstance();


			userinput=stdin.nextLine().trim();
			if(userinput.startsWith("!login ")) {
				String[] input = userinput.split(" ");
				if(input.length==2) {
					this.clientChallenge = securityService.generateBase64RandomNumber(32);
					userinput+=" "+tcpport+" "+new String(this.clientChallenge);//+udpport;
					input = userinput.split(" ");
					if(initKeys(input[1]) == true) {
						outputThread.setChallenge(clientChallenge);
						myChannel = SecureChannel.getInstance();
						SecureChannel.getInstance().setSecurityService(securityService);
					}
				}
				
			}
			
			this.lastMessage = userinput;
			
			
			if(keysOk || userinput.equals("!list")) myChannel.send(userinput);
			else keysOk = true;
			
			if(userinput.startsWith("!confirm ") && userinput.split(" ").length == 4){
				stop = true;
				while(stop && duration < 20){
					
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					duration++;
//					System.out.print(duration+" ");
				}
				
			}
			

//			System.out.println();
			//OUTGOING

			if(userinput.equals("!end")) {
				isActive = false;
			}
		}

		closeDown();


	}
	
	
	public void stopSleep(){
		stop = false;
		
	}
	

	public void groupBid(String str){
		myChannel.send("!bid " + str);
	}
	
	private boolean initKeys(String username) {
		//HMAC Secret Key

		byte[] keyBytes = new byte[1024];
		String pathToSecretKey = clientKeysPath+username+".key";
		FileInputStream fis;
		try {
			fis = new FileInputStream(pathToSecretKey);
			fis.read(keyBytes);
			fis.close();
		} catch (FileNotFoundException e2) {
			keysOk = false;
			System.out.println("Key File for specified user not found. Aborting login request.");
			return false;
		} catch (IOException e) {
			keysOk = false;
			System.out.println("Key File for specified user not found. Aborting login request.");
			return false;
		}

		byte[] input = Hex.decode(keyBytes);

		this.hmacKey = new SecretKeySpec(input,"HmacSHA256");

		try {
			this.hmac = Mac.getInstance("HmacSHA256");
			this.hmac.init(hmacKey);
		} catch (NoSuchAlgorithmException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// MESSAGE is the message to sign in bytes
		securityService.setHMac(hmac);



		//private Key
		PEMReader in = null;
		int retries = 0;
		KeyPair keyPair = null;
		while(retries < 5)  {
			try {
				in = new PEMReader(new FileReader(clientKeysPath+username+".pem"), new PasswordFinder() {

					@Override
					public char[] getPassword() {
						// reads the password from standard input for decrypting the private key
						System.out.println("Enter pass phrase:");
						try {
							return new BufferedReader(new InputStreamReader(System.in)).readLine().toCharArray();
						} catch (IOException e) {
							e.printStackTrace();
							return null;
						}
					} 

				});
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}


			Security.addProvider(new BouncyCastleProvider());


			try {
				keyPair = (KeyPair) in.readObject();
				in.close();
				break;
			} catch (IOException e) {
				retries++;
				if(retries<5)System.out.println("Couldn't read private key. Trying again...");
			}
		}
		if(retries>=5) {
			System.out.println("Couldn't read private key! Aborting login request.");
			keysOk = false;
			return false;
		}
		this.clientKey = keyPair.getPrivate();


		//public key
		try {
			FileReader reader = new FileReader(serverPublicKeyPath);
			in = new PEMReader(reader);
			serverKey = (PublicKey) in.readObject();
		} catch (IOException e) {
			System.out.println("Server Key File not found. Aborting login request.");
			keysOk = false;
			return false;
		}


		this.securityService.setPrivateKey(clientKey);
		this.securityService.setPublicKey(serverKey);
		return true;
	}

	private void closeDown() {
		isActive=false;
		outputThread.closeThread();
		//udpOutputThread.closeThread();
		myChannel.close();
	}

	private static void usage() {
		System.out.println("Usage: java AuctionClient hostname serverport clientport\nExample: java AuctionClient localhost 12345 12345");
		System.exit(-1);
	}

	private static boolean checkInput(String[] args) {
		if(args.length!=5) {
			return false;
		}
		int tcp=-1;
		int server=-1;
		try {
			server = Integer.parseInt(args[1]);
			tcp = Integer.parseInt(args[2]);
		} catch (NumberFormatException e) {
			System.out.println("Error parsing input");
		}
		if(tcp==-1||server==-1) {
			return false;
		}
		tcpport=tcp;
		serverport=server;
		hostname=args[0];
		serverPublicKeyPath=args[3];
		clientKeysPath=args[4];

		return true;
	}

	public void serverClosed() {
		isActive = false;
		stdin.close();


	}

	public void loggedIn(boolean boo) {
		this.loggedIn = boo;
		if(boo == false) {
			myChannel = TCPChannel.getInstance();
		}
	}


//	public Queue<String> getQueue(){
//		return confirmation.getConfirmers();		
//	}
//	
//	public void addNewConfirmer(String name){
//		confirmation.addConfirmer(name);
//	}

	public void setUsername(String username) {
		//System.out.println("username: "+username);
		//udpOutputThread.setUsername(username);
	}
	
	public String getUsername(){
		return username;
	}

	public void setBlocked(boolean confirm){
		isBlocked = confirm;		
	}

	public boolean getBlocked(){
		return isBlocked;
	}

	public void setBlockCounter(){
		blockCounter++;
	}

	public int getBlockCounter(){
		return blockCounter;
	}

	@Override
	public void sendCommand(String command) {
		String output = command;
		if(output.startsWith("!login ")) {
			output+=" "+0;//+udpport;
		}
		myChannel.send(output);

	}

	@Override
	public String response() {
		return myChannel.read();
	}

	public void setServerChallenge(byte[] bytes) {
		this.serverChallenge = bytes;
		this.myChannel = AESChannel.getInstance();
		this.loggedIn = true;
	}

	public void resendLastMessage() {
		myChannel.send(this.lastMessage);		
	}

}
